设计模式
下列语言的实现主要是C#
语言
学习资料:author::设计模式概述
资料作者:author::ZShijun/DesignPattern
设计模式面试题(总结最全面的面试题!!!)-CSDN博客
一、设计模式基础 1 面向对象
特性:封装、继承、多态。
目标:设计出高内聚、低耦合的应用程序,最大程度的实现程序的复用,以应对复杂的需求变化。
设计原则:单一职责原则、依赖倒置原则、开闭原则、接口隔离原则、里氏替换原则、合成复用原则、迪米特原则。
2 设计模式
模式就是对前人积累的经验的抽象和升华。简单地说,就是从不断重复出现的事件中发现和抽象出规律,并解决同一类问题的经验总结,在软件工程领域中的模式可分为三个层次。
层次:
惯用法:最底层,语言相关,如引用计数,智能指针,垃圾收集等。
设计模式:中层,语言无关,如工厂模式,策略模式等。
架构模式:最高层,语言无关,用于高层决策,实现架构复用,如C/S架构,B/S架构,微服务架构等。
GoF设计模式根据其目的可分为三种类型:
创建型:主要用于创建对象,主要有工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型:主要用于处理类或对象的组合,主要有适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型:主要用于描述对类或对象之间的交互及职责分配,主要有策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
3 七大设计原则
单一职责原则:一个类只负责一个功能领域中的相应职责。
依赖倒置原则:
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
抽象类不应该依赖于细节类,细节类应当依赖于抽象类。
换言之,要面向接口(抽象类)编程,而不是面向实现编程。
开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
接口隔离原则:使用多个专门的接口,而不使用单一的总接口。
里氏替换原则:所有基类出现的地方必定能被子类替换,且功能不会发生影响。
合成复用原则:尽量使用对象组合/聚合,而不是继承来达到复用的目的:
组合:在初始化以后,内部的属性值不可被替换。比如我创建了一个圆形画布后,就不可以修改了,这是圆形类和画布类组合在了一起。
聚合:初始化以后,内部的属性值依然可以被替换。比如我创建了一个圆形画布后,可以修改,将圆形画布换成方形,这叫聚合。现在一般都是聚合。
迪米特原则:也叫最小知识原则,一个软件实体应当尽可能少地与其他实体发生相互作用。类与类之间的耦合度应尽量的低,这样如果类发生变化,影响才会最小。
开闭原则是目标,里氏代换原则是基础,依赖倒置原则是手段。
核心思想就是隔离变化,针对接口编程而不是针对实现编程。
二、创建型 1 工厂模式 1.1 Factory概念
工厂顾名思义就是创建产品,本质就是用工厂方法代替new
操作创建一种实例化对象的方式。根据不同的实现方式和抽象级别又可分为简单工厂,工厂方法和抽象工厂三种模式。
1.2 简单工厂模式
简单工厂又叫做静态工厂方法模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
一般情况下使用较好,也有人认为简单工厂模式是一种反模式。
优点:
实现了对责任的分割,隔离了变化,因为它提供了专门的工厂类用于创建对象。
通过配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:
集中了所有实例的创建逻辑,违反了单一职责原则。
扩展困难,一旦添加新产品就不得不修改工厂逻辑,违反了开闭原则。
public static class DbConnectionFactory { private static readonly string _connectionString = @"Server=(LocalDB)\MSSQLLocalDB; Integrated Security=true;Initial Catalog=FactoryDb" ; private static readonly string _dbType = "Sqlserver" ; public static DbConnection CreateDbConnection () { if (_dbType == "Sqlserver" ) { return new SqlConnection(_connectionString); } else if (_dbType == "MySql" ) { return new MySqlConnection(_connectionString); } else { return null ; } } } public class SqlHelper { public int ExecuteNonQuery (string sql ) { using (DbConnection conn = DbConnectionFactory.CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; return cmd.ExecuteNonQuery(); } } } public object ExecuteScalar (string sql ) { using (DbConnection conn = DbConnectionFactory.CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; return cmd.ExecuteScalar(); } } } } class Program { static void Main (string [] args ) { SqlHelper sqlHelper = new SqlHelper(); string selectSql = "select count(*) from [Users];" ; object count = sqlHelper.ExecuteScalar(selectSql); Console.WriteLine($"共有{count} 记录" ); } }
interface IProduct { void ShowInfo () ; } class ConcreteProductA : IProduct { public void ShowInfo () { Console.WriteLine("This is Concrete Product A." ); } } class ConcreteProductB : IProduct { public void ShowInfo () { Console.WriteLine("This is Concrete Product B." ); } } class SimpleFactory { public IProduct CreateProduct (string productName ) { Type type = Type.GetType(productName); if (type == null || !typeof (IProduct).IsAssignableFrom(type)) { throw new ArgumentException("Invalid product name." ); } return Activator.CreateInstance(type) as IProduct; } } class Program { static void Main () { SimpleFactory factory = new SimpleFactory(); IProduct productA = factory.CreateProduct("ConcreteProductA" ); productA.ShowInfo(); IProduct productB = factory.CreateProduct("ConcreteProductB" ); productB.ShowInfo(); } }
1.3 工厂方法模式
定义一个工厂父类,工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。即将类的实例化延迟到工厂类的子类中完成,即由子类来决定应该实例化哪一个类。
优点:符合设计原则。
缺点:类的个数成倍增加,增加了系统的复杂度。
public class MySqlConnectionFactory : DbConnectionFactory { private static readonly string _connectionString = @"..." ; public override DbConnection CreateDbConnection () { return new MySqlConnection(_connectionString); } } public class OracleSqlConnectionFactory : DbConnectionFactory { private static readonly string _connectionString = @"..." ; public override DbConnection CreateDbConnection () { return null ; } } public class SqlConnectionFactory : DbConnectionFactory { private static readonly string _connectionString = @"Server=(LocalDB)\MSSQLLocalDB; Integrated Security=true;Initial Catalog=FactoryDb" ; public override DbConnection CreateDbConnection () { return new SqlConnection(_connectionString); } } public class SqlHelper { DbConnectionFactory _connectionFactory; public SqlHelper (DbConnectionFactory connectionFactory ) { this ._connectionFactory = connectionFactory; } public int ExecuteNonQuery (string sql ) { using (DbConnection conn = _connectionFactory.CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; return cmd.ExecuteNonQuery(); } } } public object ExecuteScalar (string sql ) { using (DbConnection conn = _connectionFactory.CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; return cmd.ExecuteScalar(); } } } } class Program { static void Main (string [] args ) { SqlHelper sqlHelper = new SqlHelper(new SqlConnectionFactory()); string selectSql = "select count(*) from [Users];" ; object count = sqlHelper.ExecuteScalar(selectSql); Console.WriteLine($"共有{count} 记录" ); } }
1.4 抽象工厂模式
抽象工厂是工厂方法的升级版,为相关或者相互依赖的对象提供一个统一的接口,而且无需指定他们的具体实现类。
比如我要对在工厂方法中扩展对sql参数的自定义,那么我需要再添加参数工厂类,Mysql参数类,Oracle参数类等,这样会产生非常多的类。
因此工厂方法模式的问题:
抽象工厂的特点:
public class SqlDbFactory : DbFactory { private static readonly string _connectionString = @"Server=(LocalDB)\MSSQLLocalDB; Integrated Security=true;Initial Catalog=FactoryDb" ; public override DbConnection CreateDbConnection () { return new SqlConnection(_connectionString); } public override DbParameter CreateDbParameter (string parameterName, object value ) { return new SqlParameter(parameterName, value ); } } public abstract class DbFactory { public abstract DbConnection CreateDbConnection () ; public abstract DbParameter CreateDbParameter (string parameterName, object value ) ; } public class SqlHelper { DbFactory _dbFactory; public SqlHelper (DbFactory dbFactory ) { _dbFactory = dbFactory; } public int ExecuteNonQuery (string sql,params DbParameter[] parms ) { using (DbConnection conn = _dbFactory.CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; if (parms != null ) { cmd.Parameters.AddRange(parms); } return cmd.ExecuteNonQuery(); } } } public object ExecuteScalar (string sql, params DbParameter[] parms ) { using (DbConnection conn = _dbFactory.CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; if (parms != null ) { cmd.Parameters.AddRange(parms); } return cmd.ExecuteScalar(); } } } public DbParameter CreateDbParameter (string parameterName, object value ) { return _dbFactory.CreateDbParameter(parameterName, value ); } } class Program { static void Main (string [] args ) { DbFactory dbFactory = new SqlDbFactory(); SqlHelper sqlHelper = new SqlHelper(dbFactory); int age = 12 ; string selectSql = $"select count(*) from [Users] where Age>@Age;" ; object count = sqlHelper.ExecuteScalar(selectSql, new DbParameter[] { sqlHelper.CreateDbParameter("@Age" , age) }); Console.WriteLine($"共有{count} 记录" ); } }
2 单例模式 2.1 Singleton概念
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
目的:
优点:
import threadingimport timeclass Singleton : instance = None def __init__ (self, name ): self.name = name def __new__ (cls, *args, **kwargs ): if cls.instance: return cls.instance cls.instance = object .__new__(cls) return cls.instance obj1 = Singleton('Alex' ) print (obj1)obj2 = Singleton('Bob' ) print (obj2)import threadingclass Singleton : _instance_lock = threading.Lock() instance = None def __init__ (self, name ): self.name = name def __new__ (cls, *args, **kwargs ): if not cls.instance: with cls._instance_lock: if not cls.instance: cls.instance = super ().__new__(cls) return cls.instance obj1 = Singleton('Alex' ) print (obj1)obj2 = Singleton('Bob' ) print (obj2)
2.2 静态类实现
直接设置一个静态类,不是单例模式,但可以满足需求,常用,可用于生产。
注意:这里的++_counter
其实存在高并发问题,严格上应该用Interlocked.Increment(ref _counter)
的方式。
优点:使用起来方便,简单。
缺点:
静态类不能继承类,实现接口,不能通过接口或者抽象方法(虚方法)实现多态。
静态类必须在第一次加载时初始化,如果项目中用不到会导致资源浪费。(资源消耗较小的话,可以适当忽略)
public static class SingletonSample1 { private static int _counter = 0 ; public static int IncreaseCount () { return ++_counter; } } public static class SingletonSample1 { private static int _counter = 0 ; public static int IncreaseCount () { return Interlocked.Increment(ref _counter); } } class Program { static void Main (string [] args ) { int count1 = singletonSample1.IncreaseCount(); int count2 = SingletonSample1.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); } }
2.3 内部初始化
在内部进行静态初始化,可用于生产。
优点:解决了静态类不能继承类,实现接口,不能通过接口或者抽象方法(虚方法)实现多态的问题。
缺点:没有解决第一次加载时初始化,资源浪费的问题。
public sealed class SingletonSample2 { private static readonly SingletonSample2 _instance = new SingletonSample2(); private int _counter = 0 ; private SingletonSample2 () { } public static SingletonSample2 Instance { get { return _instance; } } public int IncreaseCount () { return ++_counter; } } class Program { static void Main (string [] args ) { int count1 = singletonSample2.Instance.IncreaseCount(); int count2 = SingletonSample2.Instance.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); } }
2.4 过渡类
过渡阶段,不可用于生产。
SingletonSample3,加if判断:
没有预热,这不叫高并发,叫并发高 - 知乎 (zhihu.com)
SingletonSample4,加锁:
public class SingletonSample3 { private static SingletonSample3 _instance; private int _counter = 0 ; private SingletonSample3 () { } public static SingletonSample3 Instance { get { if (_instance == null ) { _instance = new SingletonSample3(); } return _instance; } } public int IncreaseCount () { return ++_counter; } } public class SingletonSample4 { private static SingletonSample4 _instance; private static readonly object _locker = new object (); private int _counter = 0 ; private SingletonSample4 () { } public static SingletonSample4 Instance { get { lock (_locker) { if (_instance == null ) { _instance = new SingletonSample4(); } return _instance; } } } public int IncreaseCount () { return ++_counter; } } class Program { static void Main (string [] args ) { int count1 = singletonSample3.Instance.IncreaseCount(); int count2 = SingletonSample3.Instance.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); count1 = singletonSample4.Instance.IncreaseCount(); count2 = SingletonSample4.Instance.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); } }
2.5 双检锁实现
if套lock,设置双重锁,,可用于生产。
优点:解决了上述实现方式的各种设计缺陷。
缺点:代码有点复杂。
注:C#
中依然有些问题。因为_instance = new SingletonSample5();
并不是一步完成的。通常情况下,这一步是先开辟空间,再赋值,再将_instance
指向这一段空间。而有些编译器会将该步骤优化成先开辟空间,然后就将_instance
指向这一段空间,最后赋值。因此会导致这段空间还没被赋值,_instance
就已经指向此处了。若此时还有新的请求到达,那么将会直接返回空的_instance
,这是存在问题的。解决方法就是在_instance
前加上volatile
关键字。
public class SingletonSample5 { private static volatile SingletonSample5 _instance; private static readonly object _locker = new object (); private int _counter = 0 ; private SingletonSample5 () { } public static SingletonSample5 Instance { get { if (_instance == null ) { lock (_locker) { if (_instance == null ) { _instance = new SingletonSample5(); } } } return _instance; } } public int IncreaseCount () { return ++_counter; } } class Program { static void Main (string [] args ) { int count1 = singletonSample5.Instance.IncreaseCount(); int count2 = SingletonSample5.Instance.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); } }
2.6 懒加载模式
.Net
支持的一种优雅版本的实现方式,强烈建议使用该版本 。类似内部初始化方式。
如何使用 C# 中的 Lazy - 知乎 (zhihu.com)
Lazy:将对象的创建延迟到第一次需要使用时。
优点:代码优雅简洁同时满足需求。
缺点:当系统中有大量单例模式时,会有较多重复代码。
public class SingletonSample6 { private static readonly Lazy<SingletonSample6> _instance = new Lazy<SingletonSample6>(() => new SingletonSample6()); private int _counter = 0 ; private SingletonSample6 () { } public static SingletonSample6 Instance { get { return _instance.Value; } } public int IncreaseCount () { return ++_counter; } } class Program { static void Main (string [] args ) { int count1 = singletonSample6.Instance.IncreaseCount(); int count2 = SingletonSample6.Instance.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); } }
2.7 懒加载泛型
将懒加载需要重复写的代码封装了起来,拥有多个单例类时建议使用此版本。
优点:封装了重复代码。
缺点:违反了依赖倒置原则。
public class SingletonSampleBase <TSingleton > where TSingleton : class { private static readonly Lazy<TSingleton> _instance = new Lazy<TSingleton>(() => (TSingleton)Activator.CreateInstance(typeof (TSingleton), true )); protected SingletonSampleBase () { } public static TSingleton Instance { get { return _instance.Value; } } } public class SingletonSample7 : SingletonSampleBase <SingletonSample7 >{ private int _counter = 0 ; private SingletonSample7 () { } public int IncreaseCount () { return ++_counter; } } class Program { static void Main (string [] args ) { int count1 = singletonSample7.Instance.IncreaseCount(); int count2 = SingletonSample7.Instance.IncreaseCount(); Console.WriteLine($"count1={count1} ,count2={count2} " ); } }
2.8 单例案例
数据库单例案例,实际中不要用单例做对数据库的操作。
实际不使用的原因:
线程安全性: 使用单例模式会引入全局共享的对象实例,可能存在多线程并发访问时的安全性问题,需要额外考虑线程安全措施。而每次操作数据库时都创建一个新的数据库连接或者使用连接池,可以更好地控制线程安全性。
资源消耗: 单例模式会在应用程序启动时就创建对象实例并一直存在于内存中,占用资源较多。而数据库连接需要进行资源管理,及时释放连接资源可以更好地避免资源耗尽的问题。
灵活性:每次操作数据库时可以根据需要灵活地创建新的数据库连接或者使用现有连接,而不受单例对象的限制,更适合在需要时动态创建和释放资源。
public class MySqlHelper : SqlHelperBase <MySqlHelper >{ private static readonly string _connectionString = @"..." ; private MySqlHelper () { } public override DbConnection CreateDbConnection () { return new MySqlConnection(_connectionString); } public override DbParameter CreateDbParameter (string parameterName, object value ) { return new MySqlParameter(parameterName, value ); } } public interface ISqlHelper { int ExecuteNonQuery (string sql ) ; object ExecuteScalar (string sql ) ; DbConnection CreateDbConnection () ; DbParameter CreateDbParameter (string parameterName, object value ) ; } public class SqlHelper : SqlHelperBase <SqlHelper >{ private static readonly string _connectionString = @"Server=(LocalDB)\MSSQLLocalDB; Integrated Security=true;Initial Catalog=FactoryDb" ; private SqlHelper () { } public override DbConnection CreateDbConnection () { return new SqlConnection(_connectionString); } public override DbParameter CreateDbParameter (string parameterName, object value ) { return new SqlParameter(parameterName, value ); } } public abstract class SqlHelperBase <TSingleton >: ISqlHelper where TSingleton : class { private static readonly Lazy<TSingleton> _instance = new Lazy<TSingleton>(() => (TSingleton)Activator.CreateInstance(typeof (TSingleton), true )); public static TSingleton Instance { get { return _instance.Value; } } public int ExecuteNonQuery (string sql ) { using (DbConnection conn = CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; return cmd.ExecuteNonQuery(); } } } public object ExecuteScalar (string sql ) { using (DbConnection conn = CreateDbConnection()) { using (DbCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; return cmd.ExecuteScalar(); } } } public abstract DbConnection CreateDbConnection () ; public abstract DbParameter CreateDbParameter (string parameterName, object value ) ; } class Program { static void Main (string [] args ) { ISqlHelper sqlHelper = MySqlHelper.Instance; string selectSql = "select count(*) from [Users];" ; object count = sqlHelper.ExecuteScalar(selectSql); Console.WriteLine($"共有{count} 记录" ); } }
3 建筑者模式 3.1 Builder概念
定义:建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建复杂的、具有复合属性的对象。
角色:
建造者(Builder):不同种类的工人,如打地基的,建房梁的,室内装修的等。
具体的建造者(ConcreteBuilder):每个工种对应的具体的工人。
指挥者(Director):工程队总指挥,包工头,指挥具体的建造者建房子。
具体产品(Product):最终建成的房子。
3.2 最简单建筑者
设置好主要对象和对象属性,自己进行组装。
优点:简单,并且配置可灵活搭配。
缺点:面向了实现编程,用户需要知道太多的创建细节。
public abstract class Cpu { public abstract string Type { get ; set ; } }public class HighCpu :Cpu { public override string Type { get ; set ; } = "6核12线程" ; }public class LowCpu :Cpu { ... }public abstract class Mem { public abstract string Type { get ; set ; } }public class HighCpu :Mem { ... }public class LowCpu :Mem { ... }public abstract class Screen { public abstract string Type { get ; set ; } }public class HighCpu :Screen { ... }public class LowCpu :Screen { ... }public class Phone { public Cpu Cpu { get ; set ; } public Screen Screen { get ; set ; } public Mem Mem { get ; set ; } public void Show () { Console.WriteLine("手机配置:" ); Console.WriteLine($"CPU:{Cpu?.Type} " ); Console.WriteLine($"内存:{Mem?.Type} " ); Console.WriteLine($"屏幕:{Screen?.Type} " ); } } class Program { static void Main (string [] args ) { Phone highPhone = new Phone(); highPhone.Cpu = new HighCpu(); highPhone.Mem = new HighMem(); highPhone.Screen = new HighScreen(); highPhone.Show(); Console.WriteLine(); Phone lowPhone = new Phone(); lowPhone.Cpu = new LowCpu(); lowPhone.Mem = new LowMem(); lowPhone.Screen = new LowScreen(); lowPhone.Show(); } }
3.3 工厂方法建筑
定义一个工厂来完成类属性的创建,可以屏幕创造细节,但是类会非常多。
优点:
缺点:
复杂度急剧增大,类爆炸。
把配件的组装交给手机类(Phone)处理不合理。
没有屏蔽手机创造细节。
public abstract class Cpu { public abstract string Type { get ; set ; } }public abstract class CpuBuilder { public abstract Cpu BuildCpu () ; }public class HighCpu :Cpu { public override string Type { get ; set ; } = "6核12线程" ; }public class HighCpuBuilder : CpuBuilder { public override Cpu BuildCpu () { return new HighCpu(); } } public class LowCpu : Cpu { public override string Type { get ; set ; } = "双核4线程" ; }public class LowCpuBuilder : CpuBuilder { public override Cpu BuildCpu () { return new LowCpu(); } } ... public class Phone { public Phone (ScreenBuilder screenBuilder, CpuBuilder cpuBuilder, MemBuilder memBuilder ) { this .Screen = screenBuilder.BuildScreen(); this .Cpu = cpuBuilder.BuildCpu(); this .Mem = memBuilder.BuildMem(); } public Cpu Cpu { get ; set ; } public Screen Screen { get ; set ; } public Mem Mem { get ; set ; } public void Show () { Console.WriteLine("手机配置:" ); Console.WriteLine($"CPU:{Cpu?.Type} " ); Console.WriteLine($"内存:{Mem?.Type} " ); Console.WriteLine($"屏幕:{Screen?.Type} " ); } } class Program { static void Main (string [] args ) { ScreenBuilder highScreenBuilder = new HighScreenBuilder(); CpuBuilder highCpuBuilder = new HighCpuBuilder(); MemBuilder highMemBuilder = new HighMemBuilder(); Phone phone = new Phone(highScreenBuilder, highCpuBuilder, highMemBuilder); phone.Show(); Console.WriteLine(); ScreenBuilder lowScreenBuilder = new LowScreenBuilder(); CpuBuilder lowCpuBuilder = new LowCpuBuilder(); MemBuilder lowMemBuilder = new LowMemBuilder(); Phone lowPhone = new Phone(lowScreenBuilder, lowCpuBuilder, lowMemBuilder); lowPhone.Show(); } }
3.4 标准建筑者模式
利用抽象工厂+简单工厂实现,简单工厂负责生成最终的对象,抽象工厂负责对最终对象的相关联的属性进行组合。
和工厂模式的区别:工厂模式是创建了一组相关联的对象,然后对象之间进行操作。而建筑者模式是在内部对关联对象直接进行操作,最终只生成一个更大的对象。
优点:
一定程度上,消除了类爆炸问题。
职责分离,由单独一个生产线组装手机。
缺点:
配件配置变得固定了,不能随意组合。
对大多数场景依然过于复杂,比如,未必每一个配置的手机都需要一个生产线,组装手机也未必需要一个单独的生产线。
public abstract class Cpu { public abstract string Type { get ; set ; } }public class LowCpu :Cpu { public override string Type { get ; set ; } = "双核4线程" ; }public class HighCpu :Cpu { public override string Type { get ; set ; } = "6核12线程" ; }... public class LowPhonePartBuilder : PhonePartBuilder { public override Cpu BuildCpu () { return new LowCpu(); } public override Mem BuildMem () { return new LowMem(); } public override Screen BuildScreen () { return new LowScreen(); } } public class HighPhonePartBuilder : PhonePartBuilder { public override Cpu BuildCpu () { return new HighCpu(); } public override Mem BuildMem () { return new HighMem(); } public override Screen BuildScreen () { return new HighScreen(); } } public abstract class PhonePartBuilder { public abstract Cpu BuildCpu () ; public abstract Mem BuildMem () ; public abstract Screen BuildScreen () ; } public class PhoneFactory { private PhonePartBuilder _phonePartBuilder; public PhoneFactory (PhonePartBuilder phonePartBuilder ) { _phonePartBuilder = phonePartBuilder; } public void SetPhoneBuilder (PhonePartBuilder phonePartBuilder ) { _phonePartBuilder = phonePartBuilder; } public Phone BuildPhone () { Phone phone = new Phone(); phone.Cpu = _phonePartBuilder?.BuildCpu(); phone.Mem = _phonePartBuilder?.BuildMem(); phone.Screen = _phonePartBuilder?.BuildScreen(); return phone; } } class Program { static void Main (string [] args ) { PhonePartBuilder highPartBuilder = new HighPhonePartBuilder(); PhoneFactory phoneFactory = new PhoneFactory(highPartBuilder); Phone highPhone = phoneFactory.BuildPhone(); highPhone.Show(); Console.WriteLine(); phoneFactory.SetPhoneBuilder(new LowPhonePartBuilder()); Phone lowPhone = phoneFactory.BuildPhone(); lowPhone.Show(); } }
3.5 优化建筑者模式
只保留一个最终对象的建筑者,其他的通过接口在内部实现。
优点:简单,灵活,代码优雅。
缺点:用户使用成本相对较高,需要使用者自己配置内部参数。
public class Cpu { public string Type { get ; set ; } }public class Mem { public string Type { get ; set ; } }public class Screen { public string Type { get ; set ; } }public class Phone { public Cpu Cpu { get ; set ; } public Screen Screen { get ; set ; } public Mem Mem { get ; set ; } public void Show () { Console.WriteLine("手机配置:" ); Console.WriteLine($"CPU:{Cpu?.Type} " ); Console.WriteLine($"内存:{Mem?.Type} " ); Console.WriteLine($"屏幕:{Screen?.Type} " ); } } public interface IPhoneBuilder { IPhoneBuilder BuildCpu (Action<Cpu> buildCpuDelegate ) ; IPhoneBuilder BuildMem (Action<Mem> buildMemDelegate ) ; IPhoneBuilder BuildScreen (Action<Screen> buildScreenDelegate ) ; Phone Build () ; } public class PhoneBuilder :IPhoneBuilder { private Phone _phone; private Cpu _cpu; private Mem _mem; private Screen _screen; public IPhoneBuilder BuildCpu (Action<Cpu> buildCpuDelegate ) { _cpu = new Cpu(); buildCpuDelegate?.Invoke(_cpu); return this ; } public IPhoneBuilder BuildMem (Action<Mem> buildMemDelegate ) { _mem = new Mem(); buildMemDelegate?.Invoke(_mem); return this ; } public IPhoneBuilder BuildScreen (Action<Screen> buildScreenDelegate ) { _screen = new Screen(); buildScreenDelegate?.Invoke(_screen); return this ; } public Phone Build () { _phone = new Phone(); _phone.Cpu = _cpu??new Cpu() { Type="4核8线程" }; _phone.Mem = _mem??new Mem() { Type = "8G" }; _phone.Screen = _screen??new Screen() { Type = "7寸" }; return _phone; } } class Program { static void Main (string [] args ) { IPhoneBuilder phoneBuilder = new PhoneBuilder(); Phone phone = phoneBuilder .BuildCpu(cpu => { cpu.Type = "8核16线程" ; }) .BuildMem(mem => { mem.Type = "32G" ; }) .BuildScreen(screen => { screen.Type = "10寸" ; }) .Build(); phone.Show(); } }
4 原形模式 4.1 Prototype概念
定义:原型模式是用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。简单地说就是,首先创建一个实例,然后通过这个实例去拷贝(克隆)创建新的实例。
案例:通常情况下,找工作时,需要准备多份简历,简历信息大致相同,但是可以根据不同的公司的岗位需求微调工作经历细节,以及薪资要求。
使用场景:
当需要重复创建一个包含大量公共属性,而只需要修改少量属性的对象时。
当需要重复创建一个初始化需要消耗大量资源的对象时。
优点:创建大量重复的对象,同时保证性能。
注意:
尽量将实现原型模式的类标记为sealed
。
尽量避免使用ICloneable
接口。
4.2 手动克隆
手动将克隆的内容写在Clone中。因为新加内容后需要修改源码,因此违背了开闭原则。
该方法要多次new了ItResume对象(可以通过观察构造函数的执行次数发现),一样需要消耗资源。
public class BasicInfo { public string Name { get ; set ; } public string Gender { get ; set ; } public int Age { get ; set ; } public string ExpectedSalary { get ; set ; } } public class WorkExperence { public string Company { get ; set ; } public string Detail { get ; set ; } public DateTime StartDate { get ; set ; } public DateTime EndDate { get ; set ; } public void Display () { Console.WriteLine("工作经历:" ); Console.WriteLine($"{this .Company} \t{this .StartDate.ToShortDateString()} -{EndDate.ToShortDateString()} " ); Console.WriteLine("工作详细:" ); Console.WriteLine(this .Detail); } } public abstract class ResumeBase { public string Name { get ; set ; } public string Gender { get ; set ; } public int Age { get ; set ; } public string ExpectedSalary { get ; set ; } public abstract void Display () ; public abstract ResumeBase Clone () ; } public class ItResume : ResumeBase { public ItResume () {} public WorkExperence WorkExperence { get ; set ; } public override void Display () { Console.WriteLine($"姓名:\t{this .Name} " ); Console.WriteLine($"性别:\t{this .Gender} " ); Console.WriteLine($"年龄:\t{this .Age} " ); Console.WriteLine($"期望薪资:\t{this .ExpectedSalary} " ); Console.WriteLine("--------------------------------" ); if (this .WorkExperence != null ) { this .WorkExperence.Display(); } Console.WriteLine("--------------------------------" ); } public override ResumeBase Clone () { ItResume resume = new ItResume() { Name = this .Name, Gender = this .Gender, Age = this .Age, ExpectedSalary = this .ExpectedSalary, WorkExperence = new WorkExperence { Company = this .WorkExperence.Company, Detail = this .WorkExperence.Detail, StartDate = this .WorkExperence.StartDate, EndDate = this .WorkExperence.EndDate } }; return resume; } } public interface IResumeBuilder { IResumeBuilder BuildBasicInfo (Action<BasicInfo> buildBasicInfoDelegate ) ; IResumeBuilder BuildWorkExperence (Action<WorkExperence> buildWorkExperenceDelegate ) ; ResumeBase Build () ; } public class ResumeBuilder : IResumeBuilder { private readonly BasicInfo _basicInfo = new BasicInfo(); private readonly WorkExperence _workExperence = new WorkExperence(); public IResumeBuilder BuildBasicInfo (Action<BasicInfo> buildBasicInfoDelegate ) { buildBasicInfoDelegate?.Invoke(_basicInfo); return this ; } public IResumeBuilder BuildWorkExperence (Action<WorkExperence> buildWorkExperenceDelegate ) { buildWorkExperenceDelegate?.Invoke(_workExperence); return this ; } public ResumeBase Build () { ItResume resume = new ItResume() { Name = this ._basicInfo.Name, Gender = this ._basicInfo.Gender, Age = this ._basicInfo.Age, ExpectedSalary = this ._basicInfo.ExpectedSalary, WorkExperence = new WorkExperence { Company = this ._workExperence.Company, Detail = this ._workExperence.Detail, StartDate = this ._workExperence.StartDate, EndDate = this ._workExperence.EndDate } }; return resume; } } class Program { static void Main (string [] args ) { IResumeBuilder resumeBuilder = new ResumeBuilder() .BuildBasicInfo(resume => { resume.Name = "张三" ; resume.Age = 18 ; resume.Gender = "男" ; resume.ExpectedSalary = "100W" ; }) .BuildWorkExperence(work => { work.Company = "A公司" ; work.Detail = "负责XX系统开发,精通YY。。。。。" ; work.StartDate = DateTime.Parse("2019-1-1" ); work.EndDate = DateTime.Parse("2020-1-1" ); }); ResumeBase resume1 = resumeBuilder .Build(); ItResume resume2 = resume1.Clone() as ItResume; resume2.ExpectedSalary = "面议" ; resume2.WorkExperence.Detail = "aaaaaaaaaa" ; resume1.Display(); resume2.Display(); } }
4.3 浅拷贝克隆
部分内容直接浅拷贝,需要深拷贝的内容将手动进行拷贝。一样违背了开闭原则。
该方法只需要new了一次ItResume对象(可以通过观察构造函数的执行次数发现),其他的通过拷贝完成,相对消耗资源较少。
public class WorkExperence { ... public WorkExperence Clone () { return this .MemberwiseClone() as WorkExperence; } } public override ResumeBase Clone (){ ItResume itResume = this .MemberwiseClone() as ItResume; itResume.WorkExperence = this .WorkExperence.Clone(); return itResume; }
4.4 序列化克隆
通过序列化对象,可以将对象转换为字节流,然后再从字节流中反序列化出一个新的对象,实现了对象的深复制,从而实现了克隆的效果。最推荐。
[Serializable ] public class WorkExperence {}[Serializable ] public abstract class ResumeBase {}[Serializable ] public sealed class ItResume : ResumeBase {}public override ResumeBase Clone (){ using (MemoryStream stream = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(stream, this ); stream.Position = 0 ; return bf.Deserialize(stream) as ResumeBase; } }
4.5 ICloneable克隆
利用C#
自带的ICloneable进行克隆。
基本思想如下:
由于只有一个Clone方法,因此调用者无法区分到底是深拷贝还是浅拷贝,会给调用者造成极大的困扰;
如果基类继承了ICloneable
接口,并且非Sealed类型,那么它的所有派生类都需要实现Clone方法。否则,用派生类对象调用Clone方法,返回的对象将会是基类Clone方法创建的对象,这就给派生类带来了沉重的负担,因此在非密封类中应该避免实现 ICloneable
接口,但这个不是ICloneable
特有的缺陷,任何一种方式实现原型模式都存在该问题,因此建议将原型模式的实现类设置为密封类。
Clone方法返回值是object
,是非类型安全的;
ICloneable
被很多人认为是一个糟糕的设计,其他理由如下:
ICloneable
除了标识可被克隆之外,无论作为参数还是返回值都没有任何意义;
.Net Framework
在升级支持泛型至今,都没有添加一个与之对应的ICloneable<T>
泛型接口;
很多框架中为了向下兼容,虽然实现了ICloneable
接口,但是内部只提供了一个抛出异常的私有实现,例如SqlConnection
。
鉴于上述诸多缺点,在实现原型模式时,ICloneable
接口能不用就不要用了,自己定义一个更有意义的方法或许会更好。
[Serializable ] public sealed class ItResume : ICloneable { public string Name { get ; set ; } public string Gender { get ; set ; } public int Age { get ; set ; } public string ExpectedSalary { get ; set ; } public WorkExperence WorkExperence { get ; set ; } public void Display () { Console.WriteLine($"姓名:\t{this .Name} " ); Console.WriteLine($"性别:\t{this .Gender} " ); Console.WriteLine($"年龄:\t{this .Age} " ); Console.WriteLine($"期望薪资:\t{this .ExpectedSalary} " ); Console.WriteLine("--------------------------------" ); if (this .WorkExperence != null ) { this .WorkExperence.Display(); } Console.WriteLine("--------------------------------" ); } public object Clone () { using (MemoryStream stream = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(stream, this ); stream.Position = 0 ; return bf.Deserialize(stream); } } }
三、结构型 1 适配器模式 1.1 Adapter概念
定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式将原本由于接口不兼容而不能一起工作的那些类可以一起工作。类似于读卡器。
优点:
可以让任何两个没有关联的类一起运行。
提高了类的复用。
通过引入一个适配器类来重用现有的类,而无须修改原有结构,遵守了开闭原则。
缺点:过多地使用适配器,会让系统非常零乱,不易整体进行把握。
总结:适配器模式属于补偿机制,专门用来在系统后期扩展、修改时使用。因此,适配器模式也不宜过度使用,如果可以的话,我们应该优先通过重构解决。
1.2 电脑适配器案例
角色:
目标接口:Target,我们期望的接口。
适配器:Adapter,将被适配者转换成我们期望的形式。
被适配者:Adaptee,原有的接口。
public class Computer { private IUsb _usb; public void SetUsb (IUsb usb ) { _usb = usb; } public void ConnectUsb () { if (_usb != null ) { _usb.Request(); } } } public interface IUsb { void Request () ; } public class SdReader : IUsb { private Sd _sd; public SdReader (Sd sd ) { _sd = sd; } public void Request () { _sd.ReadWrite(); } } public class Sd { public void ReadWrite () { Console.WriteLine("存取数据" ); } }class Program { static void Main (string [] args ) { Computer computer = new Computer(); Sd sd = new Sd(); computer.SetUsb(new SdReader(sd)); computer.ConnectUsb(); } }
2 代理模式 2.1 Proxy概念
定义:为其他对象提供一种代理以控制对这个对象的访问。例如防火墙、VPN等。
目的:
在不改变原有代码的基础上,对原有类加以控制。
访问由于某种原因不能直接访问或者直接访问困难的第三方组件或中间件。
和适配器比较:
适配器模式的目的是接口转换,使原本不兼容而不能一起工作的类可以一起工作。
代理模式的目的是间接访问和访问控制。
适配器模式面向的是不能一起工作的两个类,而代理模式是面向原本可以一起工作的两个类。
总结:随着系统复杂度的发展,代理模式更偏向于是一种架构模式,在各种框架中以及与各种中间件交互是是非常常见的,而在我们自己的代码中反而很少见了,它更多的体现的是一种思想,而非代码实现。
2.2 图片加载案例
通过代理模式,可以对图片更好地进行操作。当图片展示时,如果图片已经加载过了,就不用再重复加载,提高了效率。这种延迟加载的方式可以优化系统性能,减少资源消耗。
这里体现了和适配器模式的不同之处,适配器模式需要进行适配才能正常使用,而代理模式原件本身是可以直接使用的,只是通过代理对象可以更加方便和安全地对原件进行控制和管理。代理模式提供了对原始对象的间接访问,通过代理对象可以在不改变原始对象的情况下,增加额外的功能或控制访问权限。
using System;public interface IImage { void Display () ; } public class RealImage : IImage { private string _fileName; public RealImage (string fileName ) { _fileName = fileName; LoadImageFromDisk(); } private void LoadImageFromDisk () { Console.WriteLine("Loading image from disk: " + _fileName); } public void Display () { Console.WriteLine("Displaying image: " + _fileName); } } public class ProxyImage : IImage { private RealImage _realImage; private string _fileName; public ProxyImage (string fileName ) { _fileName = fileName; } public void Display () { if (_realImage == null ) { _realImage = new RealImage(_fileName); } _realImage.Display(); } } class Program { static void Main () { IImage image = new ProxyImage("test.jpg" ); image.Display(); image.Display(); } }
3 外观模式 3.1 Facade概念
定义:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
目的:
为一个复杂的模块或子系统提供一个一致的外界访问接口,降低客户端访问子系统的复杂度。
使客户端与子系统之间解耦,让子系统内部模块更易维护和扩展。
进行访问控制,提高系统安全性。
维护大型遗留系统。
外观模式跟代理模式类似,也更偏向于架构模式,常见于企业应用集成中,企业应用集成包括界面集成,业务流程集成(过程集成),控制集成(应用集成,API集成),数据集成四个层面,都与外观模式有密切关系。
外观模式简单来说就是开发好了多个功能,然后为它们做好界面并进行组装。
和代理模式之间的相同点
都可以在不改变子系统代码的基础上,对子系统加以控制;
原有系统之间都是可以直接访问的,两个模式都是为了让子系统更加容易使用;
都不应该在其中添加子系统没有的功能。
和代理模式之间的不同点:
外观模式面向的是多个不同的子系统,而代理模式通常面向的是一个(或者多个相同的)子系统。
外观模式更多强调是对多个子系统的整合,而代理模式更多强调的是对某个子系统的代理。
极端情况下,假如外观模式中的子系统只有一个,就跟代理模式差不多了,这有点像抽象工厂模式和工厂方法模式之间的关系。
3.2 回家案例
外观模式可以将回家后的多个单体操作(如开空调、开灯、关电视、关窗)聚合成一组操作,例如回家、睡觉、出门。这样可以简化用户的操作流程,提高代码的可读性和可维护性。
public class AirConditioner { public void TurnOn () { Console.WriteLine("开空调..." ); } public void TurnOff () { Console.WriteLine("关空调..." ); } } public class Light { public void TurnOn () { Console.WriteLine("开灯..." ); } public void TurnOff () { Console.WriteLine("关灯..." ); } } public class Tv { public void TurnOn () { Console.WriteLine("打开电视机..." ); } public void TurnOff () { Console.WriteLine("关闭电视机..." ); } } public class Window { public void Open () { Console.WriteLine("开窗..." ); } public void Close () { Console.WriteLine("关窗..." ); } } public class OneClickFacade { private static readonly Window _window = new Window(); private static readonly Tv _tv = new Tv(); private static readonly Light _light = new Light(); private static readonly AirConditioner _airConditioner = new AirConditioner(); public void BackHome () { Console.WriteLine("--------回家--------" ); _tv.TurnOn(); _light.TurnOn(); _airConditioner.TurnOn(); _window.Open(); } public void Sleep () { Console.WriteLine("--------睡觉--------" ); _window.Close(); _tv.TurnOff(); _light.TurnOff(); _airConditioner.TurnOn(); } public void LeaveHome () { Console.WriteLine("--------出门--------" ); _window.Close(); _tv.TurnOff(); _light.TurnOff(); _airConditioner.TurnOff(); } } class Program { static void Main (string [] args ) { OneClickFacade oneClickFacade = new OneClickFacade(); oneClickFacade.BackHome(); oneClickFacade.Sleep(); oneClickFacade.LeaveHome(); } }
4 装饰器模式 4.1 Decorator概念
装饰器模式是一种结构型设计模式,它允许在不改变原有对象结构的情况下,动态地添加额外的功能或责任。该模式通过创建一组装饰类来包装原始类,每个装饰类负责添加一部分功能,通过嵌套调用来实现功能层层叠加。
在装饰器模式中,每个具体装饰器类继承自一个通用的抽象装饰器类,同时持有一个指向被装饰对象的引用。这样,客户端可以透明地使用被装饰对象和装饰对象,它们之间的差异对客户端来说是透明的。装饰器模式可以避免继承的混乱,让类的功能可以动态地组合和追加。
总结来说,装饰器模式提供了一种灵活的方式来扩展对象的功能,同时保持对象的简单性和独立性。
装饰器模式跟代理模式类图基本一样,但是,它们之间却有很大的区别:
装饰器模式关注于在一个对象上动态的添加方法,而代理模式关注于控制对对象的访问。
装饰器模式通常用聚合的方式,而代理模式通常采用组合的方式。
装饰器模式通常会套用多层,而代理模式通常只有一层。 但是由于他们的结构十分相似,因此很多时候二者可以做同样的事,比如装饰器模式和代理模式都可用于实现AOP(面向切面编程)。
4.2 奶茶非装饰器案例
案例的需求是基类有饮料和配料,然后饮料分奶茶、咖啡等,配料分冰、布丁等。
非装饰器模式中,饮料和配料是独立的,在饮料中加配料的功能是写在饮料中的。
优点:
可任意搭配组合,并且满足新增饮品的需求。
新增饮品和配料均只需要增加新的类即可,满足开闭原则。
缺点:
依然需要修改饮料类,这在很多时候是不被允许的,或者说做不到的。
Add方法不合理,饮料不应该具有添加配料的能力。
public class Bing : Peiliao { public override string Name => "冰" ; public override int Price => 0 ; } public class Buding : Peiliao { public override string Name => "布丁" ; public override int Price => 2 ; } public class Hongdou : Peiliao { public override string Name => "红豆" ; public override int Price => 1 ; } public class Tang : Peiliao { public override string Name => "糖" ; public override int Price => 0 ; } public class Zhenzhu : Peiliao { public override string Name => "珍珠" ; public override int Price => 3 ; } public class Kafeibanlv : Peiliao { public override string Name => "咖啡伴侣" ; public override int Price => 1 ; } public abstract class Peiliao { public abstract string Name { get ; } public abstract int Price { get ; } } public class Naicha : Drink { public Naicha () { Name = "奶茶" ; Price = 8 ; } } public class Kafei : Drink { public Kafei () { Name = "咖啡" ; Price = 12 ; } } public abstract class Drink { protected List<Peiliao> Peiliaos = new List<Peiliao>(); public string Name { get ; set ; } public int Price { get ; set ; } public int Cost { get { int cost = this .Price; foreach (var peiliao in Peiliaos) { cost += peiliao.Price; } return cost; } } public string Desc { get { string desc = this .Name; foreach (var peiliao in Peiliaos) { desc += "+" + peiliao.Name; } return desc; } } public void AddPeiliao (Peiliao peiliao ) { Peiliaos.Add(peiliao); } } class Program { static void Main (string [] args ) { Drink drink = new Naicha(); drink.AddPeiliao(new Bing()); drink.AddPeiliao(new Hongdou()); drink.AddPeiliao(new Hongdou()); drink.AddPeiliao(new Zhenzhu()); Console.WriteLine($"描述:{drink.Desc} ,价格:{drink.Cost} " ); Drink drink2 = new Kafei(); drink2.AddPeiliao(new Kafeibanlv()); Console.WriteLine($"描述:{drink2.Desc} ,价格:{drink2.Cost} " ); } }
4.3 奶茶装饰器案例
通过组合+继承的方式改进,可使得饮品的扩展更灵活,同时也遵守了开闭原则。其中,组合是为了实现功能,而继承是为了约束类型,这其实就是装饰者模式。
在下面的代码中,配料不再在饮料中添加,而是在配料中添加功能,通过传递饮料来确定哪个饮料需要加配料(配料组合饮料的同时继承饮料)。
优点:
可动态的给一个对象增加额外的职责。
有很好地可扩展性。
缺点:增加了程序的复杂度,刚接触理解起来会比较困难。
public class Bing : Peiliao { public Bing (Drink drink ) : base (drink ) { Name = "冰" ; Price = 0 ; } } public class Buding : Peiliao { public Buding (Drink drink ) : base (drink ) { Name = "布丁" ; Price = 2 ; } } public class Hongdou : Peiliao { public Hongdou (Drink drink ) : base (drink ) { Name = "红豆" ; Price = 1 ; } } public class Tang : Peiliao { public Tang (Drink drink ) : base (drink ) { Name = "糖" ; Price = 0 ; } } public class Zhenzhu : Peiliao { public Zhenzhu (Drink drink ) : base (drink ) { Name = "珍珠" ; Price = 3 ; } } public class Kafeibanlv : Peiliao { public Kafeibanlv (Drink drink ) : base (drink ) { Name = "咖啡伴侣" ; Price = 1 ; } } public class Peiliao : Drink { protected readonly Drink Drink; public Peiliao (Drink drink ) { Drink = drink; } public override string Desc { get { return Drink.Desc + "+" + this .Name; } } public override int Cost { get { return Drink.Cost + this .Price; } } } public class Naicha : Drink { public Naicha () { Name = "奶茶" ; Price = 8 ; } public override string Desc => this .Name; public override int Cost => this .Price; } public class Kafei : Drink { public Kafei () { Name = "咖啡" ; Price = 12 ; } public override string Desc => this .Name; public override int Cost => this .Price; } public abstract class Drink { public string Name { get ; set ; } public int Price { get ; set ; } public abstract string Desc { get ; } public abstract int Cost { get ; } } class Program { static void Main (string [] args ) { Drink drink = new Naicha(); drink = new Hongdou(drink); drink = new Bing(drink); drink = new Buding(drink); Console.WriteLine($"描述:{drink.Desc} ,价格:{drink.Cost} " ); Drink drink2 = new Kafei(); drink2 = new Kafeibanlv(drink2); drink2 = new Tang(drink2); Console.WriteLine($"描述:{drink2.Desc} ,价格:{drink2.Cost} " ); } }
5 桥接模式 5.1 Bridge概念
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。
在桥接模式中,抽象部分定义了对象的行为和属性,而实现部分则负责实现这些行为和属性。通过将抽象部分和实现部分独立出来,我们可以在不影响彼此的情况下,扩展或修改它们。桥接模式通过组合关系而非继承关系,避免了类的爆炸性增长,并更灵活地适应不同需求的变化。
跟装饰器模式的区别:
装饰器模式是为了动态地给一个对象增加功能,而桥接模式是为了让类在多个维度上自由扩展。
装饰器模式的装饰者和被装饰者需要继承自同一父类,而桥接模式通常不需要。
装饰器模式通常可以嵌套使用,而桥接模式不能。
5.2 奶茶桥接案例
在奶茶点单场景中,将品牌、容量和饮品分离开来,通过组合关系来实现不同品牌、不同容量的饮品组合。
饮品是作用的主体,品牌、容量等是附加属性,组合在饮品当中。
public class CoCo : BrandBase { public override string BrandName => "[CoCo]" ; public override int Price => 2 ; } public class Ruixin : BrandBase { public override string BrandName => "[瑞辛]" ; public override int Price => 2 ; } public class Xicha : BrandBase { public override string BrandName => "[喜茶]" ; public override int Price => 3 ; } public class Yidiandian : BrandBase { public override string BrandName => "[一点点]" ; public override int Price => 2 ; } public abstract class BrandBase { public abstract string BrandName { get ; } public abstract int Price { get ; } } public class Dabei : SkuBase { public override string SkuType => "大杯" ; public override int Price => 3 ; } public class Zhongbei : SkuBase { public override string SkuType => "中杯" ; public override int Price => 2 ; } public class Xiaobei : SkuBase { public override string SkuType => "小杯" ; public override int Price => 1 ; } public abstract class SkuBase { public abstract string SkuType { get ; } public abstract int Price { get ; } } public class Naicha : Drink { public Naicha (BrandBase brand, SkuBase sku ):base (brand,sku ) { Name = "奶茶" ; Price = 8 ; } } public class Kafei : Drink { public Kafei (BrandBase brand, SkuBase sku ) : base (brand, sku ) { Name = "咖啡" ; Price = 12 ; } } public abstract class Drink { private readonly BrandBase _brand; private readonly SkuBase _sku; public Drink (BrandBase brand, SkuBase sku ) { this ._brand = brand; this ._sku = sku; } public string Name { get ; set ; } public int Price { get ; set ; } public string Desc { get { return this .Name + this ._brand.BrandName + this ._sku.SkuType; } } public int Cost { get { return this .Price + this ._brand.Price + this ._sku.Price; } } } class Program { static void Main (string [] args ) { BrandBase brand = new Ruixin(); SkuBase sku = new Dabei(); Drink drink = new Kafei(brand, sku); Console.WriteLine($"描述:{drink.Desc} ,价格:{drink.Cost} " ); } }
6 组合模式 6.1 Composite概念
定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得对单个对象和组合对象的使用具有一致性。
案例:文件系统中的文件与文件夹、Winform中的简单控件与容器控件、XML中的Node和Element等。
优点:
客户端调用简单,可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
可以方便的在结构中增加或者移除对象。
缺点:客户端需要花更多时间理清类之间的层次关系。
6.2 透明模式
透明模式是把组合使用的方法放到抽象类中,使得叶子对象和枝干对象具有相同的结构,客户端调用时具备完全一致的行为接口。但因为Leaf类本身不具备Add()、Remove()方法的功能,所以实现它是没有意义的,违背了单一职责原则和里氏替换原则。
public class CatalogService { #region 数据 private static readonly List<Node> _nodes = new List<Node> { new Node{Id=1 ,Name="音乐" ,ParentId=0 }, new Node{Id=2 ,Name="知识" ,ParentId=0 }, new Node{Id=3 ,Name="生活" ,ParentId=0 }, new Node{Id=4 ,Name="科学科普" ,ParentId=2 }, new Node{Id=5 ,Name="社科人文" ,ParentId=2 }, new Node{Id=6 ,Name="职业职场" ,ParentId=2 }, new Node{Id=7 ,Name="野生技术协会" ,ParentId=2 }, new Node{Id=8 ,Name="搞笑" ,ParentId=3 }, new Node{Id=9 ,Name="日常" ,ParentId=3 }, new Node{Id=10 ,Name="摄影" ,ParentId=7 }, new Node{Id=11 ,Name="编程" ,ParentId=7 }, new Node{Id=12 ,Name="英语" ,ParentId=7 }, new Node{Id=13 ,Name="科学科普文章1" ,ParentId=4 }, new Node{Id=14 ,Name="科学科普文章2" ,ParentId=4 }, new Node{Id=15 ,Name="摄影文章1" ,ParentId=10 }, new Node{Id=16 ,Name="编程文章1" ,ParentId=11 }, new Node{Id=17 ,Name="编程文章2" ,ParentId=11 } }; #endregion } public class Node { public int Id { get ; set ; } public string Name { get ; set ; } public int ParentId { get ; set ; } }
public class Leaf : Component { public Leaf (string name ) : base (name ) {} public override void Add (Component component ) { throw new InvalidOperationException("叶子节点不能添加元素" ); } public override void Remove (Component component ) { throw new InvalidOperationException("叶子节点不能删除元素" ); } public override int SumArticleCount () { return 1 ; } public override void Display (int depth ) { Console.WriteLine(new string ('-' , depth) + Name); } } public class Composite : Component { private List<Component> _components = new List<Component>(); public Composite (string name ):base (name ) {} public override void Add (Component component ) { _components.Add(component); } public override void Remove (Component component ) { _components.Remove(component); } public override void Display (int depth ) { Console.WriteLine(new string ('-' , depth) + Name); foreach (Component component in _components) { component.Display(depth + 1 ); } } public override int SumArticleCount () { int count = 0 ; foreach (var item in _components) { count += item.SumArticleCount(); } return count; } } public abstract class Component { public string Name { get ; set ; } public Component (string name ) { this .Name = name; } public abstract int SumArticleCount () ; public abstract void Add (Component component ) ; public abstract void Remove (Component component ) ; public abstract void Display (int depth ) ; } class Program { static void Main (string [] args ) { Component root = new Composite("目录" ); Component music = new Composite("音乐" ); Component knowledge = new Composite("知识" ); Component life = new Composite("生活" ); root.Add(music); root.Add(knowledge); root.Add(life); Component science = new Composite("科学科普" ); Component tech = new Composite("野生技术协会" ); knowledge.Add(science); knowledge.Add(tech); Component scienceArticle1 = new Leaf("科学科普文章1" ); Component scienceArticle2 = new Leaf("科学科普文章2" ); science.Add(scienceArticle1); science.Add(scienceArticle2); Component shoot = new Composite("摄影" ); Component program = new Composite("编程" ); Component english = new Composite("英语" ); tech.Add(shoot); tech.Add(program); tech.Add(english); Component shootArticle1 = new Leaf("摄影文章1" ); Component lifeArticle1 = new Leaf("生活文章1" ); Component lifeArticle2 = new Leaf("生活文章2" ); shoot.Add(shootArticle1); life.Add(lifeArticle1); life.Add(lifeArticle2); tech.Remove(program); knowledge.Display(0 ); Console.WriteLine("文章数:" + knowledge.SumArticleCount()); } }
6.3 安全模式
安全模式是把枝干和叶子节点区分开来,枝干单独拥有用来组合的方法,这种方法比较安全。但枝干和叶子节点不具有相同的接口,客户端的调用需要做相应的判断,违背了依赖倒置原则。
public class Leaf : Component { public Leaf (string name ) : base (name ) {} public override int SumArticleCount () { return 1 ; } public override void Display (int depth ) { Console.WriteLine(new string ('-' , depth) + Name); } } public class Composite : Component { private List<Component> _components = new List<Component>(); public Composite (string name ):base (name ) {} public void Add (Component component ) { _components.Add(component); } public void Remove (Component component ) { _components.Remove(component); } public override void Display (int depth ) { Console.WriteLine(new string ('-' , depth) + Name); foreach (Component component in _components) { component.Display(depth + 1 ); } } public override int SumArticleCount () { int count = 0 ; foreach (var item in _components) { count += item.SumArticleCount(); } return count; } } public abstract class Component { public string Name { get ; set ; } public Component (string name ) { this .Name = name; } public abstract int SumArticleCount () ; public abstract void Display (int depth ) ; } class Program { static void Main (string [] args ) { Composite root = new Composite("目录" ); Composite music = new Composite("音乐" ); Composite knowledge = new Composite("知识" ); Composite life = new Composite("生活" ); root.Add(music); root.Add(knowledge); root.Add(life); Composite science = new Composite("科学科普" ); Composite tech = new Composite("野生技术协会" ); knowledge.Add(science); knowledge.Add(tech); Component scienceArticle1 = new Leaf("科学科普文章1" ); Component scienceArticle2 = new Leaf("科学科普文章2" ); science.Add(scienceArticle1); science.Add(scienceArticle2); Composite shoot = new Composite("摄影" ); Composite program = new Composite("编程" ); Composite english = new Composite("英语" ); tech.Add(shoot); tech.Add(program); tech.Add(english); Component shootArticle1 = new Leaf("摄影文章1" ); Component lifeArticle1 = new Leaf("生活文章1" ); Component lifeArticle2 = new Leaf("生活文章2" ); shoot.Add(shootArticle1); life.Add(lifeArticle1); life.Add(lifeArticle2); root.Display(0 ); Console.WriteLine("文章数:" + root.SumArticleCount()); } }
7 享元模式 7.1 Flyweight概念
定义:运用共享技术有效地支持大量细粒度的对象。简单来说就是类的复用,类似于单例模式,但是享元模式是很多个类的实例。
在享元模式中,其实存在共享出来的类和非共享出来的类。但是非共享出来的类用得比较少,一般如果类是非共享状态,那么也就没有创建它的必要了。
适用场景:
系统会用到大量相同或相似的对象。
对象创建比较耗时。
目的:
案例:活字印刷、图书馆借书、共享单车。
应用实例:字符串驻留池、线程池、数据库连接池等。
优点:
缺点:增加了系统的复杂度。
与单例模式的区别:
享元模式是共享大量类的大量实例,而单例是一个类一个实例。
单例模式针对的是对象的创建,而享元模式针对的是对象的管理。
单例模式不能单独创建,而享元模式可以单独创建。
与简单工厂模式的区别:
享元模式在简单工厂模式的基础上加入了缓存。
简单工厂模式的作用仅仅是创建对象,而享元模式虽然也创建对象,但其主要作用是管理对象。
7.2 内部创建型
利用单例模式 + 简单工厂模式实现对多个类的全局共享。
内部创建方式:工厂中存在已经写好了的要使用的类,使用时即可使用即可。
工厂中用一个字典存某个类是否创建过,创建过就不再重复创建。
public class ETypeface : Typeface { public override string Print () { return "E" ; } } public class HTypeface : Typeface { public override string Print () { return "H" ; } } public class LTypeface : Typeface { public override string Print () { return "L" ; } } public class OTypeface : Typeface { public override string Print () { return "O" ; } } public abstract class Typeface { public abstract string Print () ; } public class TypefaceFactory { private static readonly IDictionary<Type, Typeface> _typefaces = new Dictionary<Type, Typeface>(); private static readonly object _locker = new object (); public static Typeface GetTypeface <TTypeface >() where TTypeface : Typeface { Type type = typeof (TTypeface); if (!_typefaces.ContainsKey(type)) { lock (_locker) { if (!_typefaces.ContainsKey(type)) { Typeface typeface = Activator.CreateInstance(typeof (TTypeface)) as Typeface; _typefaces.Add(type, typeface); } } } return _typefaces[type]; } } class Program { static void Main (string [] args ) { Console.WriteLine("Hello World!" ); Console.WriteLine($"{TypefaceFactory.GetTypeface<HTypeface>().Print()} " + $"{TypefaceFactory.GetTypeface<ETypeface>().Print()} {TypefaceFactory.GetTypeface<LTypeface>().Print()} " + $"{TypefaceFactory.GetTypeface<LTypeface>().Print()} {TypefaceFactory.GetTypeface<OTypeface>().Print()} " ); } }
7.3 外部创建型
利用单例模式 + 简单工厂模式实现对多个类的全局共享。
外部创建方式:工厂中只有一个简单的基类,使用时客户端自行向工厂中加入案例并使用。
工厂中用一个字典存某个类是否创建过,创建过就不再重复创建。
public class WordTypeface : Typeface { private readonly string _word; public WordTypeface (string word ) {this ._word = word; } public override string Print () { return this ._word; } } public abstract class Typeface { public abstract string Print () ; } public class TypefaceFactory { private static readonly IDictionary<string , Typeface> _typefaces = new Dictionary<string , Typeface>(); private static readonly object _locker = new object (); public static void SetTypeface (string key, Typeface typeface ) { if (!_typefaces.ContainsKey(key)) { lock (_locker) { if (!_typefaces.ContainsKey(key)) { _typefaces.Add(key, typeface); } } } } public static Typeface GetTypeface (string key ) { if (_typefaces.ContainsKey(key)) { return _typefaces[key]; } return null ; } } class Program { static void Main (string [] args ) { Console.WriteLine("Hello World!" ); TypefaceFactory.SetTypeface("hello" , new WordTypeface("Hello" )); Console.WriteLine(TypefaceFactory.GetTypeface("hello" ).Print()); } }
四、行为型 1 模版方法模式 1.1 TemplateMethod概念
定义:定义一个操作中的算法的框架,而将一些步骤延迟到了子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
简单来说就是定义好一个模板,后续用户在使用时可以按照这个模板进行具体实现。
优点:
封装了算法骨架,提高了代码复用性,简化了使用难度。
封装不变部分,扩展可变部分,满足开闭原则。
缺点:
1.2 大象放冰箱案例
案例:把大象放入冰箱的步骤?
实现:众所周知,把大象放入冰箱需要开冰箱门,放入大象,关冰箱门三步。所以可以定义一个模板,写好这三步。然后在子类中具体实现是放入大象还是放入狗。
public class ElephantToFridge :AnimalToFridge { public override void PutIntoFridge () { Console.WriteLine("把大象放进去" ); } } public class DogToFridge : AnimalToFridge { public override void PutIntoFridge () { Console.WriteLine("把狗放进去" ); } } public abstract class AnimalToFridge { public void Do () { OpenFridge(); PutIntoFridge(); CloseFridge(); } private void OpenFridge () { Console.WriteLine("把冰箱门打开" ); } public abstract void PutIntoFridge () ; private void CloseFridge () { Console.WriteLine("把冰箱门关上" ); } } class Program { static void Main (string [] args ) { AnimalToFridge dogToFridge = new DogToFridge(); dogToFridge.Do(); } }
1.3 长颈鹿放冰箱案例
案例:把长颈鹿放入冰箱的步骤?
实现:因为大象在冰箱里,所以把长颈鹿放入冰箱需要四步,中间需要加上把大象拿出来这一步。那么在模板中,因为有的方法需要在子类实现有的不需要,所以可以写好一个钩子函数,然后子类进行选择性实现。
钩子函数:钩子就是给子类一个授权,让子类来可重定义模板方法的某些步骤,说白了就是虚函数。
public class ElephantToFridge : AnimalToFridge { protected override void PutIntoFridge () { Console.WriteLine("把大象放进去" ); } } public class GiraffeToFridge : AnimalToFridge { protected override void BeforePutIntoFridge () { Console.WriteLine("把大象弄出来" ); } protected override void PutIntoFridge () { Console.WriteLine("把长颈鹿放进去" ); } } public abstract class AnimalToFridge { public void Do () { OpenFridge(); BeforePutIntoFridge(); PutIntoFridge(); CloseFridge(); } private void OpenFridge () { Console.WriteLine("把冰箱门打开" ); } protected virtual void BeforePutIntoFridge () { } protected abstract void PutIntoFridge () ; private void CloseFridge () { Console.WriteLine("把冰箱门关上" ); } } class Program { static void Main (string [] args ) { AnimalToFridge elephant = new ElephantToFridge(); elephant.Do(); Console.WriteLine("--------------" ); AnimalToFridge giraffe = new GiraffeToFridge(); giraffe.Do(); } }
1.4 客户端自定义
利用委托,不用子类进行实现,而是在客户端中进行实现,适合算法不难,不重复使用的场景。
优点:自由度高。
缺点:调用困难,需要客户实现。
public class AnimalToFridge { public void Do (Action beforePutIntoFridge,Action putIntoFridge ) { OpenFridge(); beforePutIntoFridge?.Invoke(); putIntoFridge?.Invoke(); CloseFridge(); } private void OpenFridge () { Console.WriteLine("把冰箱门打开" ); } private void CloseFridge () { Console.WriteLine("把冰箱门关上" ); } } class Program { static void Main (string [] args ) { AnimalToFridge elephant = new AnimalToFridge(); elephant.Do(null , () => { Console.WriteLine("把大象放进去" ); }); Console.WriteLine("--------------" ); AnimalToFridge giraffe = new AnimalToFridge(); giraffe.Do(() => { Console.WriteLine("把大象弄出来" ); }, () => { Console.WriteLine("把长颈鹿放进去" ); }); } }
2 策略模式 2.1 Strategy概念
定义:定义一系列算法,把他们一个个封装起来,并且使他们可以互相替换。该模式使得算法可以独立于使用它的客户程序而变化。
应用场景:促销活动,日志、缓存等。
2.2 画板案例
策略模式对象:
Context :策略上下文,持有IStrategy
的引用,负责和具体的策略实现交互。
IStrategy :策略接口,约束一系列具体的策略算法。
ConcreteStrategy :具体的策略实现。
在下述案例中,Context就是画板,IStrategy就是绘画、填充等动作,ConcreteStrategy就是具体的工具,比如用笔画、用刷子画、用刷子填充、用油漆桶填充等。
优点:
策略可以互相替换。
解决switch-case
、if-else
带来的难以维护的问题。
策略易于扩展,满足开闭原则。
缺点:
随着策略的扩展,策略类数量会增多。
客户端必须知道每一个策略类,增加了使用难度。
public class PenStrokeStrategy : IStrokeStrategy { public void Stroke () { Console.WriteLine($"用钢笔描边图形" ); } } public class BrushStrokeStrategy : IStrokeStrategy { public void Stroke () { Console.WriteLine($"用笔刷描边图形" ); } } public class BrushFillStrategy : IFillStrategy { public void Fill () { Console.WriteLine($"用笔刷填充图形" ); } } public class BucketFillStrategy : IFillStrategy { public void Fill () { Console.WriteLine("用油漆桶填充图形" ); } } public interface IStrokeStrategy { void Stroke () ; } public interface IFillStrategy { void Fill () ; } public class Graphics { private IStrokeStrategy _strokeStrategy; private IFillStrategy _fillStrategy; public Graphics (IStrokeStrategy strokeStrategy, IFillStrategy fillStrategy ) { this ._strokeStrategy = strokeStrategy; this ._fillStrategy = fillStrategy; } public void Stroke () { this ._strokeStrategy.Stroke(); } public void Fill () { this ._fillStrategy.Fill(); } } class Program { static void Main (string [] args ) { IStrokeStrategy strokeStrategy = new PenStrokeStrategy(); IFillStrategy fillStrategy = new BrushFillStrategy(); Graphics graphics = new Graphics(strokeStrategy, fillStrategy); graphics.Stroke(); graphics.Fill(); } }
2.3 简单工厂优化
由上述案例可知,随着策略的扩展,策略类数量会增多。因此我们可以用工厂模式进行管理,下列是用简单工厂进行管理的案例。
...; public enum StrokeWith{ Pen, Brush } public enum FillWith{ Brush, Bucket } public static class StrokeStrategyFactory { public static IStrokeStrategy CreateStrokeStrategy (StrokeWith stroke ) { switch (stroke) { case StrokeWith.Pen: return new PenStrokeStrategy(); case StrokeWith.Brush: return new BrushStrokeStrategy(); default : throw new NotSupportedException("不支持的描边方式" ); } } } public static class FillStrategyFactory { public static IFillStrategy CreateFillStrategy (FillWith fill ) { switch (fill) { case FillWith.Brush: return new BrushFillStrategy(); case FillWith.Bucket: return new BucketFillStrategy(); default : throw new NotSupportedException("不支持的填充方式" ); } } } public class Graphics { private IStrokeStrategy _strokeStrategy; private IFillStrategy _fillStrategy; public Graphics (IStrokeStrategy strokeStrategy, IFillStrategy fillStrategy ) { this ._strokeStrategy = strokeStrategy; this ._fillStrategy = fillStrategy; } public void SetStrokeStrategy (IStrokeStrategy strokeStrategy ) { this ._strokeStrategy = strokeStrategy; } public void Stroke () { this ._strokeStrategy.Stroke(); } public void Fill () { this ._fillStrategy.Fill(); } } class Program { static void Main (string [] args ) { IStrokeStrategy strokeStrategy = StrokeStrategyFactory.CreateStrokeStrategy(StrokeWith.Pen); IFillStrategy fillStrategy = FillStrategyFactory.CreateFillStrategy(FillWith.Bucket); Graphics graphics = new Graphics(strokeStrategy, fillStrategy); graphics.Stroke(); graphics.Fill(); graphics.SetStrokeStrategy(StrokeStrategyFactory.CreateStrokeStrategy(StrokeWith.Brush)); } }
3 状态模式 3.1 State概念
定义:允许一个对象在其内部状态改变时改变它的行为,从而使对象看起来似乎修改了它的类。
优点:
解决switch-case
、if-else
带来的难以维护的问题;
结构清晰,提高了扩展性;
通过单例或享元可使状态在多个上下文间共享。
缺点:
随着状态的扩展,状态类数量会增多;
增加了系统复杂度,使用不当将会导致逻辑的混乱;
不完全满足开闭原则。
相比较策略模式:
强策略模式强调可以互换的算法;状态模式强调改变对象内部的状态来帮助控制自己的行为。
策略模式中用户直接与具体算法交互,决定算法的替换,需要了解算法本身;状态模式中状态是对象内部流转,用户不会直接跟状态交互,不需要了解状态本身。
策略模式策略类不需要持有Context 的引用。状态模式中状态类需要持有Context 的引用,用来实现状态转移。
3.2 有限状态机
有限状态机:发生事件(event)后,根据当前状态(cur_state) ,决定执行的动作(action) ,并设置下一个状态(nxt_state) 。
有限状态机对象:
Context :上下文环境,定义客户程序需要的接口,并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的 ConcreteState
对象来处理;
State :抽象状态,定义特定状态对应行为的接口;
ConcreteState :具体状态,实现抽象状态定义的接口。
案例:红绿灯。同时在状态机中配合了享元模式对性能进行优化(不然要多次new对象)。
注意:在状态机中使用享元模式,尽量不要将资源共享,不然在不同生态之间使用同一份资源可能会发生意想不到的结果。
public class GreenState : TrafficLightState { public override void Handle (TrafficLight light ) { Console.WriteLine("绿灯行" ); light.SetState(LightStateFactory.GetLightState<YellowState>()); } } public class RedState : TrafficLightState { public override void Handle (TrafficLight light ) { Console.WriteLine("红灯停" ); light.SetState(LightStateFactory.GetLightState<GreenState>()); } } public class YellowState : TrafficLightState { public override void Handle (TrafficLight light ) { Console.WriteLine("黄灯亮了等一等" ); light.SetState(LightStateFactory.GetLightState<RedState>()); } } public abstract class TrafficLightState { public abstract void Handle (TrafficLight light ) ; } public class TrafficLight { private TrafficLightState _currentState; public TrafficLight () { _currentState = new RedState(); } public void Turn () { if (_currentState != null ) { _currentState.Handle(this ); } } public void SetState (TrafficLightState state ) { _currentState = state; } } public class LightStateFactory { private static readonly IDictionary<Type, TrafficLightState> _lightStates = new Dictionary<Type, TrafficLightState>(); private static readonly object _locker = new object (); public static TrafficLightState GetLightState <TLightState >() where TLightState : TrafficLightState { Type type = typeof (TLightState); if (!_lightStates.ContainsKey(type)) { lock (_locker) { if (!_lightStates.ContainsKey(type)) { TrafficLightState typeface = Activator.CreateInstance(typeof (TLightState)) as TrafficLightState; _lightStates.Add(type, typeface); } } } return _lightStates[type]; } } class Program { static void Main (string [] args ) { TrafficLight light = new TrafficLight(); light.Turn(); light.Turn(); light.Turn(); light.Turn(); } }
3.3 游戏玩家状态机
案例:定义游戏玩家的状态,比如:走路,跑步等。通过状态机进行控制。
注:下列代码是在Unity中执行。
public class PlayerIdleState : PlayerGroundState { public PlayerIdleState (Player _player, PlayerStateMachine _stateMachine, string _animBoolName ) : base (_player, _stateMachine, _animBoolName ) { } public override void Enter () { base .Enter(); player.ZeroVelocity(); } public override void Exit () { base .Exit(); } public override void Update () { base .Update(); if (xInput == player.facingDir && player.IsWallDetected()) return ; if (xInput != 0 && !player.isBusy) stateMachine.ChangeState(player.moveState); } } public class PlayerGroundState : PlayerState { public PlayerGroundState (Player _player, PlayerStateMachine _stateMachine, string _animBoolName ) : base (_player, _stateMachine, _animBoolName ) { } public override void Enter () { base .Enter(); } public override void Exit () { base .Exit(); } public override void Update () { base .Update(); if (Input.GetKey(KeyCode.Mouse0)) stateMachine.ChangeState(player.primaryAttack); if (!player.IsGroundDetected()) stateMachine.ChangeState(player.airState); if (Input.GetKeyDown(KeyCode.Space) && player.IsGroundDetected()) stateMachine.ChangeState(player.jumpState); } } public class PlayerState { protected PlayerStateMachine stateMachine; protected Player player; protected float xInput; protected float yInput; private string animBoolName; public PlayerState (Player _player, PlayerStateMachine _stateMachine, string _animBoolName ) { this .player = _player; this .stateMachine = _stateMachine; this .animBoolName = _animBoolName; } public virtual void Enter () { player.animator.SetBool(animBoolName, true ); rb = player.rb; triggerCalled = false ; } public virtual void Update () { stateTimer -= Time.deltaTime; xInput = Input.GetAxisRaw("Horizontal" ); yInput = Input.GetAxisRaw("Vertical" ); player.animator.SetFloat("yVelocity" , rb.velocity.y); } public virtual void Exit () { player.animator.SetBool(animBoolName, false ); } } public class PlayerStateMachine { public PlayerState currentState { get ; private set ; } public void Initialize (PlayerState _startState ) { currentState = _startState; currentState.Enter(); } public void ChangeState (PlayerState _newState ) { currentState.Exit(); currentState = _newState; currentState.Enter(); } } using System.Collections;using System.Collections.Generic;using UnityEngine;public class Player : MonoBehaviour { public PlayerStateMachine stateMachine { get ; private set ; } public PlayerIdleState idleState { get ; private set ; } public PlayerMoveState moveState { get ; private set ; } public PlayerJumpState jumpState { get ; private set ; } public Animator animator { get ; private set ; } private void Awake () { stateMachine = new PlayerStateMachine(); idleState = new PlayerIdleState(this , stateMachine, "Idle" ); moveState = new PlayerMoveState(this , stateMachine, "Move" ); jumpState = new PlayerJumpState(this , stateMachine, "Jump" ); } private void Start () { stateMachine.Initialize(idleState); } private void Update () { stateMachine.currentState.Update(); } }
4 观察者模式 4.1 Observer概念
定义:多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
案例:聊天应用程序、消息推送服务、消息队列。
通知模式:
4.2 标准观察者模式
观察者对象:
Subject :抽象主题角色,它是一个抽象类(而实际上我用的是普通类),提供了一个用于保存观察者对象的集合和增加、删除以及通知所有观察者的方法。
ConcreteSubject :具体主题角色。
IObserver :抽象观察者角色,它是一个接口,提供了一个更新自己的方法,当接到具体主题的更改通知时被调用。
Concrete Observer :具体观察者角色,实现抽象观察者中定义的接口,以便在得到主题的更改通知时更新自身的状态。
案例:在中学阶段有一篇课文《口技》,其中有一句“遥闻深巷中犬吠,便有妇人惊觉欠伸,其夫呓语。既而儿醒,大啼。夫亦醒。”
由题可知,狗叫会引发妇人惊觉、夫呓语、儿大啼,而儿大啼又会引发夫亦醒。所以在这段关系中,狗是纯被观察者,负责发出狗叫的动作。妇人和夫是纯观察者,妇人观察儿子,夫观察儿子和狗。儿子既是观察者又是被观察者,观察狗的动作的同时被父亲观察。
优点:
主题与观察者之间的耦合低。
主题与观察者之间建立了一套触发机制,使得主题状态改变时能够及时通知观察者。
缺点:
主题与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
当观察者对象很多时,事件通知会花费很多时间,影响程序的效率。
public class Dog : Subject { public void Bark () { Console.WriteLine("遥闻深巷中犬吠" ); Publish(new EventData { Source = this , EventType = "DogBark" }); } } public class Son : Subject , IObserver { public void Update (EventData eventData ) { if (eventData.EventType == "DogBark" ) { Wakeup(); } } public void Wakeup () { Console.WriteLine("既而儿醒,大啼" ); Publish(new EventData { Source = this , EventType = "SonCry" }); } } public class Wife : IObserver { public void Update (EventData eventData ) { if (eventData.EventType == "DogBark" ) { Wakeup(); } } public void Wakeup () { Console.WriteLine("便有妇人惊觉欠伸" ); } } public class Husband : IObserver { public void DreamTalk () { Console.WriteLine("其夫呓语" ); } public void Update (EventData eventData ) { if (eventData.EventType == "DogBark" ) { DreamTalk(); } else if (eventData.EventType == "SonCry" ) { Wakeup(); } } public void Wakeup () { Console.WriteLine("夫亦醒" ); } } public class EventData { public object Source { get ; set ; } public string EventType { get ; set ; } } public class Subject { private readonly IList<IObserver> _observers = new List<IObserver>(); public void AddObserver (IObserver observer ) { _observers.Add(observer); } public void RemoveObserver (IObserver observer ) { _observers.Remove(observer); } public void Publish (EventData eventData ) { foreach (var observer in _observers) { observer.Update(eventData); } } } public interface IObserver { void Update (EventData eventData ) ; } class Program { static void Main (string [] args ) { Dog dog = new Dog(); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(); dog.AddObserver(wife); dog.AddObserver(husband); dog.AddObserver(son); son.AddObserver(husband); dog.Bark(); } }
4.3 事件总线
以下是观察者模式使用的两种典型场景,它们有何不同:
回顾前面例子中的两个问题:
dog.AddObserver(...)
真的合适吗?实际生活中,狗真的有这种能力吗?狗叫像是一种广播,而不需要专门的观察者。
因为C#
中不支持多继承,如果Dog
本身继承自Animal
的基类,如果同时作为被观察者,除了用上述演进一的实现,还能如何实现?
解决办法:将Subject
做成抽象类ISubject
,然后再设置一个Subject
实现ISubject
,将Subject
作为事件总线。之后在被观察者的代码中可以以组合的方式加入Subject
。使用时,只需要设置一个总线,初始化被观察对象时设置总线,同时向总线中加入观察者。
public class Dog { private readonly ISubject _subject; public Dog (ISubject subject ) { this ._subject = subject; } public void Bark () { Console.WriteLine("遥闻深巷中犬吠" ); _subject.Publish(new EventData { Source = this , EventType = "DogBark" }); } } public class Son : IObserver { private readonly ISubject _subject; public Son (ISubject subject ) { this ._subject = subject; } public void Update (EventData eventData ) { if (eventData.EventType == "DogBark" ) { Wakeup(); } } public void Wakeup () { Console.WriteLine("既而儿醒,大啼" ); _subject.Publish(new EventData { Source = this , EventType = "SonCry" }); } } public class Husband : IObserver { public void DreamTalk () { Console.WriteLine("其夫呓语" ); } public void Update (EventData eventData ) { if (eventData.EventType == "DogBark" ) { DreamTalk(); } else if (eventData.EventType == "SonCry" ) { Wakeup(); } } public void Wakeup () { Console.WriteLine("夫亦醒" ); } } public class Wife : IObserver { public void Update (EventData eventData ) { if (eventData.EventType == "DogBark" ) { Wakeup(); } } public void Wakeup () { Console.WriteLine("便有妇人惊觉欠伸" ); } } public class EventData { public object Source { get ; set ; } public string EventType { get ; set ; } } public class Subject : ISubject { private readonly IList<IObserver> _observers = new List<IObserver>(); public void AddObserver (IObserver observer ) { _observers.Add(observer); } public void RemoveObserver (IObserver observer ) { _observers.Remove(observer); } public void Publish (EventData eventData ) { foreach (var observer in _observers) { observer.Update(eventData); } } } public interface ISubject { void AddObserver (IObserver observer ) ; void RemoveObserver (IObserver observer ) ; void Publish (EventData eventData ) ; } public interface IObserver { void Update (EventData eventData ) ; } class Program { static void Main (string [] args ) { ISubject subject = new Subject(); Dog dog = new Dog(subject); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(subject); subject.AddObserver(wife); subject.AddObserver(husband); subject.AddObserver(son); dog.Bark(); } }
4.4 消息队列
思考:如果主题和观察者分属两个不同的系统该怎么办?
利用Broker代理:
一个Queue<EventData>
类型的队列,用于存放事件消息。
一组注册和注销观察者的方法。
一个接收来自事件发布者的事件消息的方法。
最后就是事件消息的通知机制,这里用的是定时轮询的方式,实际应用中肯定不会这么简单。
演进过程:
第一阶段主题与观察者之间的耦合低,但并没有完全解耦,这种情况主要应用在类似报纸订阅的场景。
第二阶段在主题与观察者之间加了一条总线,使得主题与观察者完全解耦,这种情况主要运用在类似黑板广播消息的场景。
第三阶段在总线与观察者之间加了一个代理,使得存在于不同系统之间的主题与观察者也能够解耦并且正常通信。
.Net中的应用:
委托(delegate
)和事件(event
)。
.Net
中提供了一组泛型接口IObserver<T>
和IObservable<T>
可用于实现事件通知机制。
...; public class Broker { private static readonly Lazy<Broker> _instance = new Lazy<Broker>(() => new Broker()); private readonly Queue<EventData> _eventDatas = new Queue<EventData>(); private readonly IList<IObserver> _observers = new List<IObserver>(); private readonly Thread _thread; private Broker () { _thread = new Thread(Notify); _thread.Start(); } public static Broker Instance { get { return _instance.Value; } } public void AddObserver (IObserver observer ) { _observers.Add(observer); } public void RemoveObserver (IObserver observer ) { _observers.Remove(observer); } private void Notify (object ? state ) { while (true ) { if (_eventDatas.Count > 0 ) { var eventData = _eventDatas.Dequeue(); foreach (var observer in _observers) { observer.Update(eventData); } } Thread.Sleep(1000 ); } } public void Enqueue (EventData eventData ) { _eventDatas.Enqueue(eventData); } } public class Subject : ISubject { public void Publish (EventData eventData ) { Broker.Instance.Enqueue(eventData); } } public interface ISubject { void Publish (EventData eventData ) ; } class Program { static void Main (string [] args ) { ISubject subject = new Subject(); Dog dog = new Dog(subject); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(subject); Broker.Instance.AddObserver(wife); Broker.Instance.AddObserver(husband); Broker.Instance.AddObserver(son); dog.Bark(); } }
5 责任链模式 5.1 ChainOfResponsibility概念
定义:多个对象都有机会处理某个请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
案例:用程序实现一个请假流程,根据请假天数不同,需要各级领导审批,例如,如果请假时长不超过1天,则直接团队负责人申请即可,超过1天不超过3天则需要项目经理审批通过才行,而超过3天不超过7天则需要CTO审批等等。
优点:请求和处理分离,请求者可以不用知道是谁处理的,处 理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性;
缺点:性能不高;调试不很方便。
==后面的作者鸽了== 6 迭代器模式 6.1 Iterator概念 7 命令模式 7.1 Command概念 8 备忘录模式 8.1 Memento概念 9 访问者模式 9.1 Visitor概念 10 中介者模式 11 解释器模式 11.1 Interpreter概念
五、其他 1 反模式
设计模式终章之反模式 - 知乎 (zhihu.com)